// ==UserScript==
// @name         Mooc Downloader
// @namespace    http://tampermonkey.net/
// @version      0.1
// @description  Mooc batch downloader(pdf,mp4)
// @author       YXP
// @match        *://www.icourse163.org/course/*
// @match        *://www.icourse163.org/spoc/course/*
// @icon         https://edu-image.nosdn.127.net/32a8dd2a-b9aa-4ec9-abd5-66cd8751befb.png
// @require      https://gitee.com/yxphope/JSExtension/raw/master/Mooc.js
// @grant        unsafeWindow
// ==/UserScript==

(function () {
  "use strict";
  // 默认已登录，懒得去试探icourse的底线，反正主打的就是免费、批量，登录又不是什么难事
  // 自己手动选择好上一次已完结的开课，点击按钮开始解析，向本地服务器发送下载命令，代为执行
  // md，mooc重载了console.log函数，根本没反应，喵的还改了eval
  // 未参加的课程只能下mp4不能下pdf
  var log = (unsafeWindow.log = console.log);
  var run = eval;
  var href = location.href;
  unsafeWindow.crsID = href.replace(/.*course\/\w*-?(.*)\?.*$/, "$1");
  unsafeWindow.termID = href.replace(/.*course\/.*\?tid=(\d*).*$/, "$1");
  var docu = document.documentElement;
  unsafeWindow.crsName = $(".course-title").innerText;
  unsafeWindow.chapters = [];
  unsafeWindow.DAT = {
    dto: {
      unitId: "id",
      finished: true,
      index: 2,
      duration: 60000,
      courseId: "course_id",
      lessonId: "lesson_id",
      videoId: 1215227185,
      termId: 1470988592,
      userId: "1478506942",
      contentType: 1,
      action: "LEARN_TIME_COUNT",
      videoTime: "durationInSeconds",
      learnedVideoTimeCount: 6,
    },
  };
  function PostLearn() {
    Post(
      "https://www.icourse163.org/web/j/courseRpcBean.saveMocContentLearn.rpc?csrfKey=c05780825c694e8d82da62e699ae05df",
      DAT,
      (r) => {
        console.log(r);
      },
      (head = {
        "Auth-Signature": "9D8812C534355FFFF029F8EA0853280C",
        "Content-Type": "application/json;charset=UTF-8",
        Cookie:
          'EDUWEBDEVICE=963123c8d2054733b329f060ed033a75; WM_TID=gBpQUT28pyVBEFREVFPFyZFPGsUjfCOS; __yadk_uid=RFNyiaoIVmKeIECv0xcW2rMVAD6elGdE; NTES_YD_PASSPORT=_jOR8K3KVSi1diINxfkLIe0rsgNyPOdkLuDsnxfRIWuvYtclYUOmhkwP6b_Lhbfh1_3E0Z8Ra7nl5REDhq55YxyFjFv2FSYEoewYGwAULSTGYK0Z0huSUHigqLLDDIesYavXh5e9im8URaX4C3vmH5pxReEabbMC.nJ3PY0wQ9VxjP7948Bt6fHhXiy9EofYUEP7h93aui.OjGz0IMJGA1F7E; NTESSTUDYSI=c05780825c694e8d82da62e699ae05df; STUDY_INFO="yd.2cfc0e3061f84c31b@163.com|8|1478506942|1698747286148"; STUDY_SESS="5Tffftn/mIjs/BDczc78Ih8BdPaJQqhLLI/Wj9HbCSkM+06ByPnctsM+7ghAV9V3MpzKKvxiXhW5GGn/+cqosyLdd+xPiowUi12DivvOemkEsBSBXTC8Uk+ZKCOnSQJr2NZt7TW/4rYIoFbgnnef7ZRo2L0dk3joD+t6PrrH62QLhur2Nm2wEb9HcEikV+3FTI8+lZKyHhiycNQo+g+/oA=="; STUDY_PERSIST="0/aNTvRA0SjUkx8wapn0nUVZfuhREykmfcy2tgMndEzf3845vUmSfQiz0/BIrdDNzJ7lkhnGYWlGrAkQKv3MMbSFKydakegmJefSpmjebzPVo1rXdMX081jateDVvCZXlxyEHPat7qzyeqnbHf+eRGtUJ+S2t2RTn75LXKeYjTxjeSgiWneDYhlQ+VkkpChz5XSSqrpCQsM3D1eiaejfBamvXzOkrstBOVIo7g+L/dHZgpjCC7Iso4RP9U87vJE8LtaQzUT1ovP2MqtW5+L3Hw+PvH8+tZRDonbf7gEH7JU="; NETEASE_WDA_UID=1478506942#|#1627967755193; WM_NI=E6CrkX5KjCqOwD%2B8nNDy79HIGmifL%2BNKoxQ3fwRVTWnMx5%2Frxv5jTF%2F%2BGhl%2BDSfIKxXcAVAIi0bUniF91YysXkDu5lj%2FljEY%2Bg6Zk68D0RlVEhxUwXnOCSSlaE90Wg91Mjk%3D; WM_NIKE=9ca17ae2e6ffcda170e2e6ee85d0469491ab91d34195ef8ea2c84a968f9e86c53af89aa6a9b87298bd88d4f12af0fea7c3b92a87ea8d93ea54f795a6a4f445b59ce5d9ed4688a7f9dacb4385b3a787cf218e95a199d439a6a99eb7d75dae93b8d6ae7abcaafb8bd74eedf58989ef21888daaaaca749cbd8fb7c443abf0aeb6ed6386e7ffbafc799cb58c96b868aaf1a48bc853e9b9fabbea3efc9c00b3ef5e87b8b8b6f654ae918db4d268a39bf7d1d66dfbef838bea37e2a3; MOOC_PRIVACY_INFO_APPROVED=true; hasVolume=true; videoVolume=0.8',
        "Edu-Script-Token": "c05780825c694e8d82da62e699ae05df",
        Nonce: "6571",
      })
    );
  }
  var csrfKey = "";
  var B = "";
  document.cookie.split("; ").forEach((v) => {
    if (!csrfKey) {
      if (v.slice(0, 11) === "NTESSTUDYSI") {
        csrfKey = v.slice(12);
      }
    }
  });
  var css = `
button#startParse {
    position: fixed;
    right: 12%;
    border: none;
    bottom: 1%;
    padding: 8px 20px;
    border-radius: 6px;
    background: aqua;
    font: bold 16px/24px sans-serif;
}
div#mooc-board {
    position: fixed;
    right: 10px;
    top: 10px;
    width: 30%;
    height: 86%;
    z-index: 9999;
    background: #fff;
    border-radius: 8px;
    padding: 15px;
    box-shadow: 1px 1px 6px 2px #5553;
    overflow: scroll;
    opacity:0.9;
}

p#crsName {
    text-align: center;
    font-weight: bold;
}

p#crsName+p {
    color: gray;
    font-size: 14px;
    text-align: center;
}

button.mooc-btn {
    font: 500 15px/15px sans-serif;
    padding: 5px 8px;
    border-radius: 6px;
    border: none;
    margin: 6px;
    position: sticky;
    bottom: 0;
    left: 0;
    background: #aee8;
}

button.mooc-btn:hover {
    background: aqua;
}

#mooc-chapters h1 {
    font-size: 18px;
    margin-top: 10px;
    color: #111;
}

#mooc-chapters h2 {
    flex: 1 0 100%;
    font-size: 16px;
    text-indent: 2ch;
    color: #333;
}
#mooc-board .res {
    display: inline-block;
    font-size: 12px;
    background: #eee9;
    padding: 4px 8px;
    max-width: 100px;
    margin: 4px 8px;
    border-radius: 4px;
    border: 1px solid #eee;
    cursor: copy;
}

.mooc-flex {
    display: flex;
    flex-direction: row;
    flex-wrap: wrap;
    justify-content: flex-start;
    align-items: stretch;
    page-break-inside:avoid;
}

.mooc-flex div {
    flex: 1 1 33%;
    position: relative;
}

.mooc-video {
    color: blue;
}

.mooc-pdf {
    color: green;
}

.mooc-pdf:after {
    content: "pdf";
    position: absolute;
    right: -8px;
    top: -7px;
    background: #f5f5dc;
    padding: 2px;
    border-radius: 4px;
    opacity:0.8;
}

.mooc-video:after {
    content: "mp4";
    position: absolute;
    right: -10px;
    top: -7px;
    background: #ddf;
    padding: 2px;
    border-radius: 4px;
    opacity:0.8;
}

#mooc-board .res.sel {
    border: 1px solid #bdf;
    background: #def;
    cursor: default;
}
#mooc-board .sort .res:before {
  content: attr(ind);
}
html.print body, html.print #mooc-board button, html.print #startParse {display:none;}
html.print #mooc-board {
    position:initial;
    width:100%;
    height:fit-content;
    box-shadow:none;
    opacity:1;
  }
html.print #mooc-board .res {max-width:160px;}
@media print{
  @page:first {margin-top:25px;}
  @page {margin:20px 15px;}
}
h1+div,h2+div {page-break-before:avoid;}
`;
  // 1. 创建面板，写入基础信息
  var board = nE("div", "mooc-board", 0, `<p id="crsName">${crsName}</p><p>ID: ${crsID}  term: ${termID}</p>`);
  var cpt = nE("div", "mooc-chapters");
  board.append(cpt);
  var but = nE("button", "startDown", "mooc-btn", "开始下载");
  but.style.cssText = "padding: 6px 10px; color: black;";
  but.title = "pdf需要参加该课程才能下载";
  board.style.display = "none";
  docu.append(board);
  but.addEventListener("click", () => {
    // for 遍历所有选中.sel的资源，请求id的签名，获取m3u8地址，发送cmd命令到本地服务器执行
    cpt.querySelectorAll(".sel").forEach(download);
  });
  var pdf = nE("button", "mooc-sel-pdf", "mooc-btn", "PDF");
  var mp4 = nE("button", "mooc-sel-mp4", "mooc-btn", "MP4");
  var all = nE("button", "mooc-sel-all", "mooc-btn", "ALL");
  var qua = nE("button", "mooc-sel-all", "mooc-btn", "低");
  var sor = nE("button", "mooc-sort", "mooc-btn", "编序");
  var sin = nE("button", "mooc-sort", "mooc-btn", "单选");
  sin.single = false;
  sor.sort = false;
  var Q = 0;
  pdf.all = mp4.all = all.all = false;
  pdf.addEventListener("click", () => {
    pdf.all = !pdf.all;
    if (pdf.all)
      cpt.querySelectorAll(".mooc-pdf").forEach((v) => {
        v.classList.add("sel");
      });
    else
      cpt.querySelectorAll(".mooc-pdf").forEach((v) => {
        v.classList.remove("sel");
      });
    all.title = `已选中 ${cpt.querySelectorAll(".sel").length}`;
  });
  mp4.addEventListener("click", () => {
    mp4.all = !mp4.all;
    if (mp4.all)
      cpt.querySelectorAll(".mooc-video").forEach((v) => {
        v.classList.add("sel");
      });
    else
      cpt.querySelectorAll(".mooc-video").forEach((v) => {
        v.classList.remove("sel");
      });
    all.title = `已选中 ${cpt.querySelectorAll(".sel").length}`;
  });
  all.addEventListener("click", () => {
    all.all = !all.all;
    if (all.all)
      cpt.querySelectorAll(".res").forEach((v) => {
        v.classList.add("sel");
      });
    else
      cpt.querySelectorAll(".res").forEach((v) => {
        v.classList.remove("sel");
      });
    all.title = `已选中 ${cpt.querySelectorAll(".sel").length}`;
  });
  sin.addEventListener("click", () => {
    sin.single = !sin.single;
    sin.style.background = sin.single ? "#8bea" : "#aee8";
  });
  qua.q = ["低", "中", "高"];
  qua.addEventListener("click", () => {
    Q = Q === 2 ? 0 : Q + 1;
    qua.innerHTML = qua.q[Q];
  });
  cpt.addEventListener("click", (e) => {
    let el = e.target;
    if (el.classList.contains("res")) {
      if (el.classList.contains("sel")) {
        el.classList.remove("sel");
      } else {
        el.classList.add("sel");
        if (sin.single) {
          download(el);
        }
      }
    }
  });
  sor.addEventListener("click", () => {
    sor.sort = !sor.sort;
    cpt.className = sor.sort ? "sort" : "";
  });
  board.append(pdf);
  board.append(mp4);
  board.append(all);
  board.append(qua);
  board.append(but);
  board.append(sor);
  board.append(sin);
  css = nE("style", "moocjs", 0, css);
  document.head.appendChild(css);
  window.addEventListener("beforeprint", () => {
    document.documentElement.className = "print";
  });
  window.addEventListener("afterprint", () => {
    document.documentElement.className = "";
  });
  /**
   * @param {string} e tagname
   * @param {string} id
   * @param {string} cla
   * @param {string} html
   * @return {Element}
   */
  function nE(e, id = undefined, cla = undefined, html = "") {
    var ele = document.createElement(e);
    id && (ele.id = id);
    cla && (ele.className = cla);
    html && (ele.innerHTML = html);
    return ele;
  }
  /**
   *
   * @param {string} s
   * @returns {Element}
   */
  function $(s) {
    return document.querySelector(s) || {};
  }
  var button = nE("button", "startParse", 0, "解析本课程");
  docu.append(button);
  button.addEventListener("click", startParse);
  function startParse() {
    board.style.display = "block";
    cpt.innerHTML = "";
    if (!termID.match(/^\d+$/)) {
      termID = unsafeWindow.termDto.termId;
    }
    // 2. 获取课程信息
    Post(
      "https://www.icourse163.org/dwr/call/plaincall/CourseBean.getMocTermDto.dwr",
      "callCount=1\nscriptSessionId=${scriptSessionId}190\nhttpSessionId=" + csrfKey + "\nc0-scriptName=CourseBean\nc0-methodName=getMocTermDto\nc0-id=0\nc0-param0=number:" + termID + "\nc0-param1=number:0\nc0-param2=boolean:true\nbatchId=15436331616",
      (r) => {
        run(r);
        if (typeof s1 !== "undefined" && s1.length > 0 && s1[0].lessons) chapters = s1;
        else if (typeof s0 !== "undefined" && s0.length > 0 && s0[0].lessons) chapters = s0;
        else if (typeof s2 !== "undefined" && s2.length > 0 && s2[0].lessons) chapters = s2;
        else if (typeof s3 !== "undefined" && s3.length > 0 && s3[0].lessons) chapters = s3;
        else if (typeof s4 !== "undefined" && s4.length > 0 && s4[0].lessons) chapters = s4;
        else if (typeof s5 !== "undefined" && s5.length > 0 && s5[0].lessons) chapters = s5;
        else { cpt.innerHTML = "<pre>" + r + "</pre>";}
        if (chapters.length) {
          for (let i = 0; i < chapters.length; i++) {
            let pt = nE("div", 0, 0, "<h1>" + chapters[i].name + "</h1>");
            let ls = chapters[i].lessons;
            cpt.append(pt);
            if (!ls) continue;
            for (let j = 0; j < ls.length; j++) {
              let lesson = nE("div", 0, "mooc-flex", "<h2>" + ls[j].name + "</h2>");
              pt.append(lesson);
              let un = ls[j].units;
              if (!un) continue;
              for (let k = 0; k < un.length; k++) {
                let l;
                if (un[k].contentType === 1) {
                  l = nE("div", 0, "mooc-video res", un[k].name);
                } else if (un[k].contentType === 3) {
                  l = nE("div", 0, "mooc-pdf res", un[k].name);
                }
                if (l) {
                  l.ind = `${i + 1}.${j + 1} `;
                  l.setAttribute("ind", l.ind);
                  l.signId = un[k].id;
                  l.contentId = un[k].contentId;
                  lesson.append(l);
                }
              }
            }
          }
        }
      }
    );
  }
  /**
   *
   * @param {string} url
   * @param {string} data
   * @param {Function} callback
   * @param {Object} head
   */
  function Post(url, data = undefined, callback = () => {}, head = {}) {
    let xhr = new XMLHttpRequest();
    xhr.open("POST", url);
    for (let i in head) {
      xhr.setRequestHeader(i, head[i]);
    }
    xhr.setRequestHeader("content-type", "application/x-www-form-urlencoded");
    xhr.onload = () => {
      let r;
      try {
        r = JSON.parse(xhr.response);
      } catch {
        r = xhr.response;
      }
      callback(r);
    };
    if (data) {
      if (typeof data === "object") data = JSON.stringify(data);
      xhr.send(data);
    } else xhr.send(data);
  }
  function download(v) {
    if (v.classList.contains("mooc-pdf")) {
      // md,pdf只让给参加了的下载,否则就是无权,除非找到开放的接口
      Post(
        "https://www.icourse163.org/dwr/call/plaincall/CourseBean.getLessonUnitLearnVo.dwr",
        "callCount=1\nscriptSessionId=${scriptSessionId}190\nhttpSessionId=" + csrfKey + `\nc0-scriptName=CourseBean\nc0-methodName=getLessonUnitLearnVo\nc0-id=0\nc0-param0=number:${v.contentId}\nc0-param1=number:3\nc0-param2=number:0\nc0-param3=number:${v.signId}\nbatchId=1691396806971`,
        (r) => {
          let s = r.indexOf('textUrl:"') + 9;
          let url = r.slice(s, r.indexOf('",', s));
          if (url.length > 10 && url.slice(0, 4) === "http") {
            s = v.innerText + ".pdf";
            $X.runcmd(`IDMan /n /d "${url}" /f "${sor.sort ? v.ind : B}${s}" /p "D:\\Download\\Video\\${crsName}"`);
            sin.single && v.classList.remove("sel");
          }
        }
      );
    } else if (v.classList.contains("mooc-video")) {
      let t = (new Date).getTime();
      let s = unsafeWindow.tokenEncrypt(v.signId, 1, 1, t);
      Post("https://www.icourse163.org/web/j/resourceRpcBean.getResourceTokenV2.rpc?csrfKey=" + csrfKey, "bizId=" + v.signId + "&bizType=1&contentType=1&sign="+s+"&timestamp="+t, (r) => {
        let sign = r.result.videoSignDto.signature;
        let xhr = new XMLHttpRequest();
        xhr.open("GET", `https://vod.study.163.com/eds/api/v1/vod/video?videoId=${v.contentId}&signature=${sign}&clientType=1`);
        xhr.onload = () => {
          let r = JSON.parse(xhr.response);
          let vid = r.result.videos;
          if (Q >= vid.length) {
            vid = vid[vid.length - 1].videoUrl;
          } else {
            vid = vid[Q].videoUrl;
          }
          let srt = r.result.srtCaptions;
          // 有字幕就下载
          if (srt && srt.length) {
            let uri = srt[0].url;
            let req = new XMLHttpRequest();
            req.open("GET", uri);
            req.onload = () => {
              $X.PostFile(`D:/Download/Video/${crsName}/${sor.sort ? v.ind : B}${v.innerText}.srt`, req.response);
            };
            req.onerror = () => {
              console.log(req.response);
            };
            req.send();
          }
          if (vid.indexOf(".m3u8") > 0) {
            $X.runcmd(`N_m3u8DL-CLI_v3.0.2 "${vid}" --workDir "D:\\Download\\Video\\${crsName}" --saveName "${sor.sort ? v.ind : B}${v.innerText}" --maxThreads 12 --minThreads 4 --retryCount 20 --timeOut 3 --enableDelAfterDone --enableMuxFastStart`);
          } else if (vid.indexOf(".mp4") > 0) {
            $X.runcmd(`IDMan /n /d "${vid}" /f "${sor.sort ? v.ind : B}${v.innerText}.mp4" /p "D:\\Download\\Video\\${crsName}"`);
          } else {
            log("Unsupport Url:", vid);
          }
          sin.single && v.classList.remove("sel");
        };
        xhr.send();
      });
    } else {
      log("unsupport type:", v);
    }
  }
})();

/*
https://www.icourse163.org/web/j/courseRpcBean.saveMocContentLearn.rpc?csrfKey=1b69669562c247698a4a6bf53e8733f0
Auth-Signature: 31ADBB92FFDA8DB8A3FFDCDEEB246082
Edu-Script-Token: 1b69669562c247698a4a6bf53e8733f0
Nonce: 6828
System: v1
Timestamp: new Date().getTime()
*/


